<?php

namespace Common\Lib\Payments\WeixinApp;

use Common\Lib\Payments\PaymentPlugin;

/**
 * @class alipay
 * @brief 微信支付
 */
class WeixinApp extends PaymentPlugin {

    //支付插件名称
    public $name = '微信支付';

    /**
     * @see paymentplugin::getSubmitUrl()
     */
    public function getSubmitUrl() {
        return 'https://api.mch.weixin.qq.com/pay/unifiedorder';
    }

    /**
     * @see paymentplugin::notifyStop()
     */
    public function notifyStop() {
        echo "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
        exit;
    }

    /**
     * @see paymentplugin::callback()
     */
    public function callback($callbackData) {
        return $this->serverCallback($callbackData);
    }

    /**
     * @see paymentplugin::serverCallback()
     */
    public function serverCallback($callbackData) {
        $this->isNotify = true;
        $xml = $GLOBALS['HTTP_RAW_POST_DATA'];
        $callbackData = $this->converArray($callbackData);
        $callbackData = array_merge($callbackData, $this->converArray($xml));
        $reply = $this->reply();
        if (isset($callbackData['result_code']) && $callbackData['result_code'] == 'SUCCESS') {
            //除去待签名参数数组中的空值和签名参数
            $para_filter = $this->paraFilter($callbackData);
            //对待签名参数数组排序
            $para_sort = $this->argSort($para_filter);
            //生成签名结果
            $mysign = $this->buildMysign($para_sort, $this->config('key'));
            //验证签名
            if ($mysign == $callbackData['sign']) {
                if ($callbackData['result_code'] == 'SUCCESS') {
                    $reply->orderNo = $callbackData['out_trade_no'];
                    $reply->money = $callbackData['total_fee'] / 100;
                    $reply->setStatus(true);
                } else {
                    $reply->message = '支付失败';
                }
            } else {
                $reply->message = '签名不匹配';
            }
        }
        return $reply;
    }

    /**
     * @see paymentplugin::notifyCallback()
     */
    public function notifyCallback($callbackData) {
        return $this->serverCallback($callbackData);
    }

    /**
     * @see paymentplugin::getSendData()
     */
    public function getSendData($payment) {

        $return = array();

        //基本参数
        $return['appid'] = $payment['appid'];
        $return['mch_id'] = $payment['mch_id'];
        $return['out_trade_no'] = $payment['M_OrderNO'];
        $return['body'] = $payment['R_Name'] ? $payment['R_Name'] : "商品";
        $return['total_fee'] = $payment['M_Amount'] * 100;
        $return['notify_url'] = $this->serverCallbackUrl;
        $return['spbill_create_ip'] = get_client_ip();
        $return['nonce_str'] = rand(10000, 99999);
        $return['trade_type'] = 'APP';
        //除去待签名参数数组中的空值和签名参数
        $para_filter = $this->paraFilter($return);

        //对待签名参数数组排序
        $para_sort = $this->argSort($para_filter);

        //生成签名结果
        $mysign = $this->buildMysign($para_sort, $payment['key']);

        //签名结果与签名方式加入请求提交参数组中
        $return['sign'] = $mysign;
        $xmlData = $this->converXML($return);
       
            
        $result = $this->curlSubmit($xmlData);
        //echo $this->serverCallbackUrl;
       // echo $this->createLinkstring($this->converArray($result));
        $result = $this->converArray($result);
        $return = [
            'appid'=>$result['appid'],
            'noncestr'=>$result['nonce_str'],
            'package'=>'Sign=WXPay',
            'partnerid'=>$result['mch_id'],
            'prepayid'=>$result['prepay_id'],
            'timestamp'=>NOW_TIME,
           
        ];
        $return['sign'] = $this->buildMysign( $this->argSort($return), $payment['key']);
        header('Content-type:text/json;charset=utf-8');       
        exit(json_encode($return));
        
    }

    /**
     * @brief 提交数据
     * @param xml $xmlData 要发送的xml数据
     * @return xml 返回数据
     */
    private function curlSubmit($xmlData) {
        //接收xml数据的文件
        $url = $this->getSubmitUrl();
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Accept: application/xml', 'Content-Type: application/xml'));
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xmlData);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);

        $response = curl_exec($ch);
        curl_close($ch);
        return $response;
    }

    
    /**
     * @brief 从array到xml转换数据格式
     * @param array $arrayData
     * @return xml
     */
    private function converXML($arrayData) {
        $xml = '<xml>';
        foreach ($arrayData as $key => $val) {

            $xml .= '<' . $key . '><![CDATA[' . $val . ']]></' . $key . '>';
        }
        $xml .= '</xml>';
        return $xml;
    }

    /**
     * @brief 从xml到array转换数据格式
     * @param xml $xmlData
     * @return array
     */
    private function converArray($xmlData) {
        $result = array();
        $xmlHandle = xml_parser_create();
        xml_parse_into_struct($xmlHandle, $xmlData, $resultArray);

        foreach ($resultArray as $key => $val) {
            if ($val['tag'] != 'XML') {
                $result[$val['tag']] = $val['value'];
            }
        }
        return array_change_key_case($result);
    }

    /**
     * 除去数组中的空值和签名参数
     * @param $para 签名参数组
     * return 去掉空值与签名参数后的新签名参数组
     */
    private function paraFilter($para) {
        $para_filter = array();
        foreach ($para as $key => $val) {
            if ($key == "sign" || $key == "sign_type" || $val == "") {
                continue;
            } else {
                $para_filter[$key] = $para[$key];
            }
        }
        return $para_filter;
    }

    /**
     * 对数组排序
     * @param $para 排序前的数组
     * return 排序后的数组
     */
    private function argSort($para) {
        ksort($para);
        reset($para);
        return $para;
    }

    /**
     * 生成签名结果
     * @param $sort_para 要签名的数组
     * @param $key 支付宝交易安全校验码
     * @param $sign_type 签名类型 默认值：MD5
     * return 签名结果字符串
     */
    private function buildMysign($sort_para, $key, $sign_type = "MD5") {
        //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
        $prestr = $this->createLinkstring($sort_para);
        //把拼接后的字符串再与安全校验码直接连接起来
        $prestr = $prestr . '&key=' . $key;
        //把最终的字符串签名，获得签名结果
        $mysgin = md5($prestr);
        return strtoupper($mysgin);
    }

    /**
     * 把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param $para 需要拼接的数组
     * return 拼接完成以后的字符串
     */
    private function createLinkstring($para) {
        $arg = "";
        foreach ($para as $key => $val) {
            $arg.=$key . "=" . $val . "&";
        }

        //去掉最后一个&字符
        $arg = trim($arg, '&');

        //如果存在转义字符，那么去掉转义
        if (get_magic_quotes_gpc()) {
            $arg = stripslashes($arg);
        }

        return $arg;
    }

    /**
     * @brief 返回配置参数
     */
    public function configParam() {
        return array(
            'channel' => 'APP端标识',
            'desc' => '描述信息',
            'appid' => '公众账号ID',
            'mch_id' => '商户号',
            'key' => 'API密钥',
        );
    }

}
